//
// Created by T on 2018/11/26.
//

#include "pkt.h"
#include "stm32f1xx_hal.h"
#include "esp8266.h"
#include "utils.h"
#include "log.h"


static struct pkt_ctx {
    // 超时时间
    uint32_t timeout;
    // 启动时间
    uint32_t tickstart;
    void (*done_cb)(struct pkt_ctx *ctx, HAL_StatusTypeDef status, void *udata);
    void *done_cb_udata;

    struct pkt pkt;
    char reply_buf[1];
    struct iovec iovec[3];
    unsigned int status;
#define PKT_CTX_BUSY            (1U)
} ctx;


static uint8_t dat[1024 + 68] = "100C652CBE7C888CCA4CC1BCD6ACD35CE5BD0BACF74D517D0ACD888D1D3DA74D340DEC5D4DCE27FD671E835D829E9B0D9F8F1D7DBCFF5D9DDD3F7F4E000FA22E224FDD4E45703D7E69807BFE8F70BC2EB6C0FE7EDCB13ABF0471788F2C01B69F5771F62F7E6230BFAA82699FD142A33FF762DA400D431050560343307F6374608733A1D0D383D060CB43FC912BC424D11E444A7151946BB1B8448E11C394AB01E8C4C6C200B4DEC23114F4025F350632718514B2531520127E252872CE752E62BDB53112B2852FB2E4452B3322552442F175193330250D134F74FB032864E7832524D0136EA4B7C349549AC361C47C53633459436A4433F339640E132ED3E4B342A3B7E324F38853420358D34E132402F122F0E2E9928302BB0248A2F4C20E228DC1D2E272B194B27D5155A2881115421960D641FAD09631E3805611B8500891DB3FBC817B3F98115B3F5B714CCEEAC11FCEB5D11D9E7AC0FE0E4590CA9E37D0663DD2A0740DABD0343D51000EED28BFF95CEADFB27CBE3F8AACA73F610C642F353C478F0D8C0A4EE52BE4EEBEEBBF8E991B9E0E722B7E9E4C3B5D5E298B487E063B308DE54B190DC48B010DA66AF18D880AE98D6E1ADD6D53CAD6ED3AFAD9BD235AD5BD105AD5BCFAFAE04CEAFAEDFCD9FAFFCCCCCB0A5CC04B20CCB75B392CB00B498CA9BB6B5CA69B8AFCA61BAD0CA58BCFBCAA0BF6BCACDC145CB43C4BBCBBDC8BACC7ECA71CD44CEF4CE2CD23BCF3CD34BD064D659D199DA04D318DE06D490";

/**
 * @brief 通断控制
 * @param pkt 请求数据包
 * @return 成功返回 1, 失败返回 0
 */
static int on_off_ctl(struct pkt_ctx *ctx, struct pkt *pkt)
{
    int powerOn = pkt->param[0];
    LOGi("on_off_ctl: powerOn=%d", powerOn);
    // TODO: 通断控制

    return 0;
}


uint8_t calc_bcc(uint8_t leading, const void *dat, size_t len)
{
    register const uint8_t *d = dat;
    while (len--)
        leading ^= *d++;
    return leading;
}

//static void set_bcc(struct pkt *pkt) {
//    pkt->param[PKT_BCC_BYTE(pkt)] = calc_bcc(0, pkt, PKT_SIZE(pkt) - 1);
//}

static int check_bcc(const struct pkt *pkt)
{
    return !calc_bcc(0, pkt, PKT_SIZE(pkt));
}


static HAL_StatusTypeDef pkt_delay(struct pkt_ctx *ctx, uint32_t timeout,
        void (*delay_done_cb)(struct pkt_ctx *ctx, HAL_StatusTypeDef status, void *udata), void *udata)
{
    if (ctx->status & PKT_CTX_BUSY) {
        return HAL_BUSY;
    }
    ctx->status |= PKT_CTX_BUSY;
    ctx->tickstart = HAL_GetTick();
    ctx->timeout = timeout;
    ctx->done_cb = delay_done_cb;
    ctx->done_cb_udata = udata;
    return HAL_OK;
}

static void net_send_done_cb(HAL_StatusTypeDef status, void *udata);
static void delay_to_report_measure(struct pkt_ctx *ctx, HAL_StatusTypeDef status, void *udata)
{
    ctx->pkt.cmd = CMD_LOGIN;
    net_send_done_cb(HAL_OK, ctx);
}

static void net_send_done_cb(HAL_StatusTypeDef status, void *udata)
{
    struct pkt_ctx *ctx = udata;
    ctx->status &= ~PKT_CTX_BUSY;

    LOGd("send_pkt_data: %hhd %s", ctx->pkt.cmd, HAL_StatusTypeDef_str(status));

    if (status != HAL_OK) {
        LOGw("send_pkt_data: %s", HAL_StatusTypeDef_str(status));
        if (status == HAL_TIMEOUT) {
            pkt_delay(ctx, 5000, delay_to_report_measure, NULL);
        }
        return;
    }

    if (ctx->pkt.cmd == CMD_LOGIN) {
        report_measure(dat, 68);
    } else if (ctx->pkt.cmd == CMD_REPORT_MEASURE) {
        report_wave(dat, 1024);
    } else if (ctx->pkt.cmd == CMD_REPORT_WAVE) {
        pkt_delay(ctx, 1000, delay_to_report_measure, NULL);
    }
}

static HAL_StatusTypeDef send_pkt_data(struct pkt_ctx *ctx, uint8_t cmd, const void *param, uint16_t param_len)
{
    if (ctx->status & PKT_CTX_BUSY) {
        return HAL_BUSY;
    }
    ctx->status |= PKT_CTX_BUSY;

    ctx->pkt.cmd = cmd;
    ctx->pkt.param_len = param_len;
    ctx->pkt.param[0] = calc_bcc(0, &ctx->pkt, PKT_HEAD_SIZE);
    ctx->pkt.param[0] = calc_bcc(ctx->pkt.param[0], param, param_len);

    ctx->iovec[0].iov_base = (char *)&ctx->pkt;
    ctx->iovec[0].iov_len = PKT_HEAD_SIZE;
    ctx->iovec[1].iov_base = param;
    ctx->iovec[1].iov_len = param_len;
    ctx->iovec[2].iov_base = (char *)ctx->pkt.param;
    ctx->iovec[2].iov_len = 1;

    HAL_StatusTypeDef status = esp8266_net_sendv(ctx->iovec, 3, net_send_done_cb, ctx);
    if (status != HAL_OK) {
        ctx->status &= ~PKT_CTX_BUSY;
    }
    return status;
}

static HAL_StatusTypeDef reply(struct pkt_ctx *ctx, uint8_t cmd, int status) {
    if (ctx->status & PKT_CTX_BUSY) {
        return HAL_BUSY;
    }
    ctx->reply_buf[0] = status;
    return send_pkt_data(ctx, cmd | 0x80, ctx->reply_buf, 1);
}

static int on_response(struct pkt_ctx *ctx, struct pkt *pkt)
{
    LOGi("%hhd command response %hhd", pkt->cmd & 0x7F, pkt->param[0]);
    return 0;
}

static const struct {
    int cmd;
    int (*handler)(struct pkt_ctx *ctx, struct pkt *pkt);
} pkt_handler_tab[] = {
        { CMD_ON_OFF_CTL           , on_off_ctl  },
        { CMD_LOGIN          | 0x80, on_response },
        { CMD_REPORT_MEASURE | 0x80, on_response },
        { CMD_REPORT_WAVE    | 0x80, on_response },
        { CMD_REPORT         | 0x80, on_response },
        { CMD_ON_OFF_CTL     | 0x80, on_response },
};

static void handle_pkt(struct pkt_ctx *ctx, int channel, struct pkt *pkt)
{
    int i, result;
    for (i = 0; i < ARR_SIZE(pkt_handler_tab); ++i) {
        if (pkt_handler_tab[i].cmd == pkt->cmd) {
            result = pkt_handler_tab[i].handler(ctx, pkt);
            if (!(pkt->cmd & 0x80)) {
                // 回复服务器
                reply(ctx, pkt->cmd, result);
            }
            break;
        }
    }
    if (i == ARR_SIZE(pkt_handler_tab)) {
        LOGw("unsupported this command 0x%02X", (int) pkt->cmd);
    }
}

void depkt_and_handle(int channel, const void *data, size_t size)
{
    struct pkt *pkt = (struct pkt *) data;
    if (size < PKT_MIN_SIZE) {
        LOGe("channel %d: invalid pkt: length too short %d", channel, (int) size);
        return;
    }
    if (PKT_SIZE(pkt) != size) {
        LOGe("channel %d: invalid pkt: PARAM_LEN %d, but pkt size %d", channel, (int) pkt->param_len, (int) size);
        return;
    }
    if (!check_bcc(pkt)) {
        LOGe("pkt BCC error");
        return;
    }
    handle_pkt(&ctx, channel, pkt);
}

void on_tcp_connected(void)
{
    HAL_GetUID((uint32_t *)dat);

    send_pkt_data(&ctx, CMD_LOGIN, dat, 12);
    TASK_SET_STAT_TO(&pkt_task, TASK_FLAG_STAT_READY);
}

HAL_StatusTypeDef report_measure(const void *data, size_t size)
{
    LOGd("report_measure");
    return send_pkt_data(&ctx, CMD_REPORT_MEASURE, data, size);
}

HAL_StatusTypeDef report_wave(const void *data, size_t size)
{
    LOGd("report_wave");
    return send_pkt_data(&ctx, CMD_REPORT_WAVE, data, size);
}

HAL_StatusTypeDef report_all(const void *data, size_t size)
{
    LOGd("report_all");
    return send_pkt_data(&ctx, CMD_REPORT, data, size);
}



static void pkt_task_fun(void)
{
    if (ctx.status & PKT_CTX_BUSY) {
        if (ctx.timeout && HAL_GetTick() - ctx.tickstart > ctx.timeout) {
            ctx.timeout = 0;
            ctx.status &= ~PKT_CTX_BUSY;
            if (ctx.done_cb) {
                ctx.done_cb(&ctx, HAL_TIMEOUT, ctx.done_cb_udata);
            }
        }
    }
}

task_t pkt_task = {
        TASK_FLAG_STAT_SLEEP,
        pkt_task_fun
};
